import 'package:english_words/english_words.dart' as english_words;
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart' as path_provider;

import 'data/todo_item.dart' show TodoItem, TodoItemAdapter;

// We use built_value and hive_generator to make serializable data class.
/* Content of the "./data/todo_item.dart" file:
import 'package:hive/hive.dart';

// Generated by `flutter packages pub run build_runner build`.
part 'todo_item.g.dart';

@HiveType(typeId: 0)
class TodoItem extends HiveObject {
  @HiveField(0)
  int id;
  @HiveField(1)
  String content;
  @HiveField(2)
  bool isDone;
  @HiveField(3)
  final DateTime createdAt;

  TodoItem({this.id, this.content, this.isDone = false, createdAt})
      : this.createdAt = createdAt ?? DateTime.now();
  
  @override
  String toString() {
    return 'TodoItem(id=$id, content=$content, idDone=$isDone, createdAt=$createdAt)';
  }
} */

class HiveExample extends StatefulWidget {
  const HiveExample({Key? key}) : super(key: key);

  @override
  _HiveExampleState createState() => _HiveExampleState();
}

class _HiveExampleState extends State<HiveExample> {
  static const kHiveFolder = 'hive';
  static const kHiveBoxName = 'todosBox';

  late Future<bool> _initDbFuture;

  @override
  void initState() {
    super.initState();
    this._initDbFuture = this._initDb();
  }

  // Initializes the hive DB, once done the hive operations are *synchronous*.
  Future<bool> _initDb() async {
    // Initialize hive.
    final dir = await path_provider.getApplicationDocumentsDirectory();
    final hiveFolder = join(dir.path, kHiveFolder);
    Hive.init(hiveFolder);
    try {
      // Normally we should register this at the app startup (i.e. in main.dart),
      // putting it here might cuase the line to run twice and lead to errors
      // since this page can be opened twice.
      Hive.registerAdapter(TodoItemAdapter());
    } on Exception catch (e) {
      print(e);
    }
    // Open the hive box so that we can later call Hive.box(<name>) to use it.
    await Hive.openBox<TodoItem>(kHiveBoxName);
    final List<TodoItem> todos = _getTodoItems();
    print('Hive initialization done, todo items in the db are:');
    todos.forEach(print);
    return true;
  }

  @override
  void dispose() {
    Hive.box(kHiveBoxName).compact();
    Hive.close();
    super.dispose();
  }

  // Retrieves records from the hive box.
  List<TodoItem> _getTodoItems() {
    final box = Hive.box<TodoItem>(kHiveBoxName);
    return box.values.toList();
  }

  // Inserts records to hive.
  // Note we don't need to explicitly set the primary key (id), it'll auto
  // increment.
  Future<void> _addTodoItem(TodoItem todo) async {
    final box = Hive.box<TodoItem>(kHiveBoxName);
    final int key = await box.add(todo);
    // Set the id field to the auto-incremented key.
    todo.id = key;
    await todo.save();
    print('Inserted: key=$key, value=$todo');
  }

  // Updates records in the db table.
  Future<void> _toggleTodoItem(TodoItem todo) async {
    // Since class TodoItem extends HiveObject, update the record is very easy.
    // Note the `todo` must already been added to the hive box.
    todo.isDone = !todo.isDone;
    await todo.save();
    print('Updated: key=${todo.id}, value=$todo');
  }

  // Deletes records in the db table.
  Future<void> _deleteTodoItem(TodoItem todo) async {
    // Since class TodoItem extends HiveObject, delete the object is very easy.
    // Note the `todo` must already been added to the hive box.
    await todo.delete();
    print('Delted: key=${todo.id}, value=$todo');
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<bool>(
      future: this._initDbFuture,
      builder: (context, snapshot) {
        if (!snapshot.hasData) {
          return const Center(
            child: CircularProgressIndicator(),
          );
        }
        return Scaffold(
          // WatchBoxBuilder by hive_flutter can save us from writing a
          // StreamBuilder ourselves.
          body: ValueListenableBuilder<Box<TodoItem>>(
            valueListenable: Hive.box<TodoItem>(kHiveBoxName).listenable(),
            builder: (context, box, _) => ListView(
              children: <Widget>[
                for (TodoItem item in box.values) _itemToListTile(item)
              ],
            ),
          ),
          floatingActionButton: _buildFloatingActionButton(),
        );
      },
    );
  }

  ListTile _itemToListTile(TodoItem todo) => ListTile(
        title: Text(
          todo.content,
          style: TextStyle(
              fontStyle: todo.isDone ? FontStyle.italic : null,
              color: todo.isDone ? Colors.grey : null,
              decoration: todo.isDone ? TextDecoration.lineThrough : null),
        ),
        subtitle: Text('id=${todo.id}\ncreated at ${todo.createdAt}'),
        isThreeLine: true,
        leading: IconButton(
          icon: Icon(
              todo.isDone ? Icons.check_box : Icons.check_box_outline_blank),
          onPressed: () => _toggleTodoItem(todo),
        ),
        trailing: IconButton(
          icon: const Icon(Icons.delete),
          onPressed: () => _deleteTodoItem(todo),
        ),
      );

  FloatingActionButton _buildFloatingActionButton() {
    return FloatingActionButton(
      onPressed: () async {
        await _addTodoItem(
          TodoItem(
            content: english_words.generateWordPairs().first.asPascalCase,
          ),
        );
      },
      child: const Icon(Icons.add),
    );
  }
}
